diff --git a/src/pixie/contexts.nim b/src/pixie/contexts.nim index 85a9f0b..7ba9e4a 100644 --- a/src/pixie/contexts.nim +++ b/src/pixie/contexts.nim @@ -482,7 +482,7 @@ proc measureText*(ctx: Context, text: string): TextMetrics {.raises: [PixieError ## text (such as its width, for example). let font = newFont(ctx) - bounds = typeset(font, text).computeBounds() + bounds = typeset(font, text).layoutBounds() result.width = bounds.x proc getLineDash*(ctx: Context): seq[float32] {.inline, raises: [].} = diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 4a5bf7e..bb8561b 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -467,7 +467,7 @@ proc typeset*( ## wrap: enable/disable text wrapping typeset(@[newSpan(text, font)], bounds, hAlign, vAlign, wrap) -proc computeBounds*(arrangement: Arrangement): Vec2 {.raises: [].} = +proc layoutBounds*(arrangement: Arrangement): Vec2 {.raises: [].} = ## Computes the width and height of the arrangement in pixels. if arrangement.runes.len > 0: for i in 0 ..< arrangement.runes.len: @@ -481,13 +481,13 @@ proc computeBounds*(arrangement: Arrangement): Vec2 {.raises: [].} = # If the text ends with a new line, we need add another line height. result.y += finalRect.h -proc computeBounds*(font: Font, text: string): Vec2 {.inline, raises: [].} = +proc layoutBounds*(font: Font, text: string): Vec2 {.inline, raises: [].} = ## Computes the width and height of the text in pixels. - font.typeset(text).computeBounds() + font.typeset(text).layoutBounds() -proc computeBounds*(spans: seq[Span]): Vec2 {.inline, raises: [].} = +proc layoutBounds*(spans: seq[Span]): Vec2 {.inline, raises: [].} = ## Computes the width and height of the spans in pixels. - typeset(spans).computeBounds() + typeset(spans).layoutBounds() proc parseOtf*(buf: string): Typeface {.raises: [PixieError].} = result = Typeface() @@ -583,6 +583,57 @@ proc textUber( else: # target is Mask target.fillPath(path, transform) +proc computeBounds*( + arrangement: Arrangement, +): Rect = + var + fullPath = newPath() + line: int + for spanIndex, (start, stop) in arrangement.spans: + let + font = arrangement.fonts[spanIndex] + underlineThickness = font.typeface.underlineThickness * font.scale + underlinePosition = font.typeface.underlinePosition * font.scale + strikeoutThickness = font.typeface.strikeoutThickness * font.scale + strikeoutPosition = font.typeface.strikeoutPosition * font.scale + for runeIndex in start .. stop: + let position = arrangement.positions[runeIndex] + + let path = font.typeface.getGlyphPath(arrangement.runes[runeIndex]) + path.transform( + translate(position) * + scale(vec2(font.scale)) + ) + + var applyDecoration = true + if runeIndex == arrangement.lines[line][1]: + inc line + if arrangement.runes[runeIndex] == SP: + # Do not apply decoration to the space at end of lines + applyDecoration = false + + if applyDecoration: + if font.underline: + path.rect( + arrangement.selectionRects[runeIndex].x, + position.y - underlinePosition + underlineThickness / 2, + arrangement.selectionRects[runeIndex].w, + underlineThickness, + font.typeface.isCCW() + ) + if font.strikethrough: + path.rect( + arrangement.selectionRects[runeIndex].x, + position.y - strikeoutPosition, + arrangement.selectionRects[runeIndex].w, + strikeoutThickness, + font.typeface.isCCW() + ) + + fullPath.addPath(path) + + fullPath.computeBounds() + proc fillText*( target: Image | Mask, arrangement: Arrangement, diff --git a/tests/fonts/PinyonScript.ttf b/tests/fonts/PinyonScript.ttf new file mode 100644 index 0000000..1bd9d29 Binary files /dev/null and b/tests/fonts/PinyonScript.ttf differ diff --git a/tests/fonts/diffs/spans7.png b/tests/fonts/diffs/spans7.png new file mode 100644 index 0000000..127555f Binary files /dev/null and b/tests/fonts/diffs/spans7.png differ diff --git a/tests/fonts/masters/spans7.png b/tests/fonts/masters/spans7.png new file mode 100644 index 0000000..873dfe6 Binary files /dev/null and b/tests/fonts/masters/spans7.png differ diff --git a/tests/fonts/rendered/spans7.png b/tests/fonts/rendered/spans7.png new file mode 100644 index 0000000..33c74f5 Binary files /dev/null and b/tests/fonts/rendered/spans7.png differ diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index 71c9b9b..c02cc25 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -16,14 +16,14 @@ block: var font = readFont("tests/fonts/Roboto-Regular_1.ttf") font.size = 24 - let bounds = font.computeBounds("Word") + let bounds = font.layoutBounds("Word") doAssert bounds == vec2(56, 28) block: var font = readFont("tests/fonts/Roboto-Regular_1.ttf") font.size = 24 - let bounds = font.computeBounds("Word\n") + let bounds = font.layoutBounds("Word\n") doAssert bounds == vec2(56, 56) block: @@ -996,6 +996,48 @@ block: doDiff(image, "spans6") +block: + + let typeface1 = readTypeface("tests/fonts/PinyonScript.ttf") + + var font1 = newFont(typeface1) + font1.size = 82 + font1.lineHeight = 60 + font1.paint = "#000000" + + let spans = @[ + newSpan("Fancy text", font1), + ] + + let image = newImage(400, 400) + image.fill(rgba(255, 255, 255, 255)) + let ctx = newContext(image) + ctx.fillStyle = "#FFD6D6" + ctx.fillRect(rect(40, 170, 320, 60)) + + let arrangement = typeset(spans, bounds = vec2(320, 60)) + + echo arrangement.layoutBounds() + echo arrangement.computeBounds() + echo arrangement.computeBounds().snapToPixels() + + let snappedBounds = arrangement.computeBounds().snapToPixels() + + let textImage = newImage(snappedBounds.w.int, snappedBounds.h.int) + textImage.fillText(arrangement, translate(-snappedBounds.xy)) + + image.draw(textImage, translate(snappedBounds.xy + vec2(40, 170))) + + # Enable this to show bounds + # ctx.strokeStyle = "#FF0000" + # ctx.translate(vec2(40, 170)) + # ctx.strokeRect(arrangement.computeBounds()) + + # Enable this to show how text is drawing directly + # image.fillText(arrangement, translate(vec2(40, 170))) + + doDiff(image, "spans7") + block: var font = readFont("tests/fonts/Roboto-Regular_1.ttf") font.size = 36