Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion DOM/Sources/DOM.PresentationAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ package extension DOM {
package var fontFamily: [DOM.FontFamily]?
package var fontSize: Float?
package var textAnchor: TextAnchor?
package var dominantBaseline: TextBaseline?

package var transform: [DOM.Transform]?
package var clipPath: DOM.URL?
Expand Down Expand Up @@ -133,7 +134,8 @@ extension DOM.PresentationAttributes {
merged.fontFamily = att.fontFamily ?? fontFamily
merged.fontSize = att.fontSize ?? fontSize
merged.textAnchor = att.textAnchor ?? textAnchor

merged.dominantBaseline = att.dominantBaseline ?? dominantBaseline

merged.transform = att.transform ?? transform
merged.clipPath = att.clipPath ?? clipPath
merged.clipRule = att.clipRule ?? clipRule
Expand Down
8 changes: 8 additions & 0 deletions DOM/Sources/DOM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ extension DOM {
case end
}

package enum TextBaseline: String {
case auto
case middle
case central
case hanging
case alphabetic
}

package enum Transform: Equatable {
case matrix(a: Float, b: Float, c: Float, d: Float, e: Float, f: Float)
case translate(tx: Float, ty: Float)
Expand Down
2 changes: 1 addition & 1 deletion DOM/Sources/Parser.XML.Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,10 @@ extension XMLParser {
el.fillOpacity = try att.parsePercentage("fill-opacity")
el.fillRule = try att.parseRaw("fill-rule")


el.fontFamily = try att.parseFontFamily("font-family")
el.fontSize = try att.parseFloat("font-size")
el.textAnchor = try att.parseRaw("text-anchor")
el.dominantBaseline = try att.parseRaw("dominant-baseline")

if let val = try? att.parseString("transform") {
el.transform = try parseTransform(val)
Expand Down
2 changes: 2 additions & 0 deletions DOM/Tests/Parser.XML.TextTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct ParserXMLTextTests {
el.attributes["font-family"] = "'Futura', sans-serif"
el.attributes["font-size"] = "12.5"
el.attributes["text-anchor"] = "end"
el.attributes["dominant-baseline"] = "central"

let parsed = try XMLParser().parseGraphicsElement(el) as? DOM.Text
let text = try #require(parsed)
Expand All @@ -55,6 +56,7 @@ struct ParserXMLTextTests {
#expect(text.attributes.fontFamily == [.name("Futura"), .keyword(.sansSerif)])
#expect(text.attributes.fontSize == 12.5)
#expect(text.attributes.textAnchor == .end)
#expect(text.attributes.dominantBaseline == .central)
}

@Test
Expand Down
6 changes: 4 additions & 2 deletions SwiftDraw/Sources/LayerTree/LayerTree.Builder.Layer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ extension LayerTree.Builder {
}
att.size = text.attributes.fontSize ?? att.size
att.anchor = text.attributes.textAnchor ?? att.anchor
let offset = Self.makeXOffset(for: text.value, with: att)
point.x += offset
let offset = Self.makeOffset(for: text.value, with: att)
point.x += offset.width
point.y += offset.height

return .text(text.value, point, att)
}

Expand Down
33 changes: 28 additions & 5 deletions SwiftDraw/Sources/LayerTree/LayerTree.Builder.Text.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,18 @@ import CoreText
extension LayerTree.Builder {

#if canImport(CoreText)
static func makeXOffset(for text: String, with attributes: LayerTree.TextAttributes) -> LayerTree.Float {
static func makeOffset(for text: String, with attributes: LayerTree.TextAttributes) -> LayerTree.Size {
let font = CGProvider.createCTFont(for: attributes.font, size: attributes.size)
let line = text.toLine(font: font)
return LayerTree.Size(
makeXOffset(line: line, anchor: attributes.anchor),
makeYOffset(font: font, baseline: attributes.baseline)
)
}

static func makeXOffset(line: CTLine, anchor: DOM.TextAnchor) -> LayerTree.Float {
let width = CTLineGetTypographicBounds(line, nil, nil, nil)
switch attributes.anchor {
switch anchor {
case .start:
return 0
case .middle:
Expand All @@ -53,13 +60,29 @@ extension LayerTree.Builder {
return LayerTree.Float(-width)
}
}

static func makeYOffset(font: CTFont, baseline: DOM.TextBaseline) -> LayerTree.Float {
let ascent = CTFontGetAscent(font)
let descent = CTFontGetDescent(font)
let emHeight = ascent + descent
switch baseline {
case .central:
return LayerTree.Float(-(emHeight / 2 - ascent))
case .alphabetic, .auto:
return 0
case .hanging:
return LayerTree.Float(-ascent)
case .middle:
let xHeight = CTFontGetXHeight(font)
return LayerTree.Float(-(xHeight / 2 - ascent))
}
}
#else
static func makeXOffset(for text: String, with attributes: LayerTree.TextAttributes) -> LayerTree.Float {
return 0
static func makeOffset(for text: String, with attributes: LayerTree.TextAttributes) -> LayerTree.Size {
return .zero
}
#endif


}

extension DOM.FontFamily {
Expand Down
6 changes: 5 additions & 1 deletion SwiftDraw/Sources/LayerTree/LayerTree.Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ extension LayerTree.Builder {
color: fill,
font: state.fontFamily.flatMap(makeFonts),
size: state.fontSize,
anchor: state.textAnchor
anchor: state.textAnchor,
baseline: state.textBaseline
)
}

Expand Down Expand Up @@ -449,6 +450,7 @@ extension LayerTree.Builder {
var fontFamily: [DOM.FontFamily]
var fontSize: DOM.Float
var textAnchor: DOM.TextAnchor
var textBaseline: DOM.TextBaseline

init() {
//default root SVG element state
Expand All @@ -468,6 +470,7 @@ extension LayerTree.Builder {
fillOpacity = 1.0
fillRule = .nonzero
textAnchor = .start
textBaseline = .auto

fontFamily = [.name("Helvetica")]
fontSize = 12.0
Expand Down Expand Up @@ -502,6 +505,7 @@ extension LayerTree.Builder {
state.fontFamily = attributes.fontFamily ?? existing.fontFamily
state.fontSize = attributes.fontSize ?? existing.fontSize
state.textAnchor = attributes.textAnchor ?? existing.textAnchor
state.textBaseline = attributes.dominantBaseline ?? existing.textBaseline

return state
}
Expand Down
1 change: 1 addition & 0 deletions SwiftDraw/Sources/LayerTree/LayerTree.Layer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ extension LayerTree {
var font: [Font]
var size: Float
var anchor: DOM.TextAnchor
var baseline: DOM.TextBaseline

enum Font: Hashable {
case truetype(Data)
Expand Down
3 changes: 2 additions & 1 deletion SwiftDraw/Tests/LayerTree/LayerTree.LayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ extension LayerTree.TextAttributes {
color: .black,
font: [.name("Times New Roman")],
size: 12.0,
anchor: .start
anchor: .start,
baseline: .auto
)
}
}
Loading