Skip to content

Commit f6e4f75

Browse files
committed
Implement image opacity.
1 parent 26810fc commit f6e4f75

14 files changed

Lines changed: 332 additions & 62 deletions

ltml/font_style_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,13 @@ func (m *mockWriter) PrintImageFile(filename string, x, y float64, width, height
143143
return 0, 0, nil
144144
}
145145

146-
func (m *mockWriter) PaintImageFile(filename string, x, y, width, height float64) error { return nil }
147-
func (m *mockWriter) PaintLinearGradient(lg *pdf.LinearGradient) error { return nil }
148-
func (m *mockWriter) PaintRadialGradient(rg *pdf.RadialGradient) error { return nil }
149-
func (m *mockWriter) PrintParagraph(para []*rich_text.RichText, opts options.Options) {}
150-
func (m *mockWriter) PrintRichText(text *rich_text.RichText) {}
146+
func (m *mockWriter) PaintImageFile(filename string, x, y, width, height, opacity float64) error {
147+
return nil
148+
}
149+
func (m *mockWriter) PaintLinearGradient(lg *pdf.LinearGradient) error { return nil }
150+
func (m *mockWriter) PaintRadialGradient(rg *pdf.RadialGradient) error { return nil }
151+
func (m *mockWriter) PrintParagraph(para []*rich_text.RichText, opts options.Options) {}
152+
func (m *mockWriter) PrintRichText(text *rich_text.RichText) {}
151153

152154
func (m *mockWriter) Pie(x, y, r, startAngle, endAngle float64, border, fill, reverse bool) error {
153155
return nil

ltml/layout_probe_writer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (w *layoutProbeWriter) PrintSVGFile(filename string, x, y float64, width, h
105105
func (w *layoutProbeWriter) PrintImageFile(filename string, x, y float64, width, height *float64) (actualWidth, actualHeight float64, err error) {
106106
return 0, 0, nil
107107
}
108-
func (w *layoutProbeWriter) PaintImageFile(filename string, x, y, width, height float64) error {
108+
func (w *layoutProbeWriter) PaintImageFile(filename string, x, y, width, height, opacity float64) error {
109109
return nil
110110
}
111111
func (w *layoutProbeWriter) PaintLinearGradient(lg *pdf.LinearGradient) error { return nil }

ltml/ltpdf/doc_writer.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,10 @@ func (dw *DocWriter) SetLineWidth(width float64) {
3131
dw.DocWriter.SetLineWidth(width, "pt")
3232
}
3333

34-
// PaintImageFile is the LTML image-as-fill hook. It currently delegates to
35-
// PrintImageFile with explicit dimensions, but keeping a separate entry point
36-
// lets LTML background/text fill code express clip-oriented paint intent.
37-
func (dw *DocWriter) PaintImageFile(filename string, x, y, width, height float64) error {
38-
_, _, err := dw.DocWriter.PrintImageFile(filename, x, y, &width, &height)
39-
return err
34+
// PaintImageFile is the LTML image-as-fill hook. It supports brush-specific
35+
// options such as uniform opacity while preserving explicit LTML sizing.
36+
func (dw *DocWriter) PaintImageFile(filename string, x, y, width, height, opacity float64) error {
37+
return dw.DocWriter.PaintImageFile(filename, x, y, width, height, opacity)
4038
}
4139

4240
func (dw *DocWriter) SetLineCapStyle(style string) (prev string) {

ltml/samples/test_045_widget_brush_backgrounds.ltml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<ltml units="in" margin="0.55">
22
<layout id="vbox" vpadding="15pt" />
3-
<font id="contrast" size="18" color="White" weight="Bold" />
3+
<font id="contrast" size="18" color="Black" weight="Bold" />
44
<brush id="linear-gradient" kind="linear-gradient" units="in" x0="0" y0="0" x1="6.4" y1="1.1" stops="0:#1d4ed8,0.5:#0f766e,1:#f59e0b" />
55
<brush id="radial-gradient" kind="radial-gradient" units="in" x0="1.45" y0="0.45" r0="0" x1="1.45" y1="0.45" r1="0.95" stops="0:#fff7ed,0.55:#fdba74,1:#c2410c" />
66
<brush id="metal" kind="image" src="../../docs/assets/metal-movable-type-banner.jpg" opacity="0.5" />
106 Bytes
Binary file not shown.

ltml/std_image_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (w *imageTestWriter) PrintImageFile(filename string, x, y float64, width, h
7373
return 0, 0, nil
7474
}
7575

76-
func (w *imageTestWriter) PaintImageFile(filename string, x, y, width, height float64) error {
76+
func (w *imageTestWriter) PaintImageFile(filename string, x, y, width, height, opacity float64) error {
7777
_, _, err := w.PrintImageFile(filename, x, y, &width, &height)
7878
return err
7979
}

ltml/std_label_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (w *labelTestWriter) PrintSVGFile(filename string, x, y float64, width, hei
149149
func (w *labelTestWriter) PrintImageFile(filename string, x, y float64, width, height *float64) (actualWidth, actualHeight float64, err error) {
150150
return 0, 0, nil
151151
}
152-
func (w *labelTestWriter) PaintImageFile(filename string, x, y, width, height float64) error {
152+
func (w *labelTestWriter) PaintImageFile(filename string, x, y, width, height, opacity float64) error {
153153
_, _, err := w.PrintImageFile(filename, x, y, &width, &height)
154154
return err
155155
}

ltml/std_widget.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,10 @@ func (widget *StdWidget) paintImageBrushInRect(w Writer, image *BrushImageStyle,
399399
if image == nil || strings.TrimSpace(image.Src) == "" {
400400
return fmt.Errorf("brush image src must be specified")
401401
}
402+
opacity := normalizeBrushOpacity(image)
403+
if opacity <= 0 {
404+
return nil
405+
}
402406
ref, err := widget.resolveBrushImageSource(image.Src)
403407
if err != nil {
404408
return err
@@ -423,7 +427,7 @@ func (widget *StdWidget) paintImageBrushInRect(w Writer, image *BrushImageStyle,
423427
}
424428

425429
return widget.paintClippedRect(w, x, y, width, height, func() error {
426-
return paintRepeatedBrushImage(w, ref.identifier, tileX, tileY, tileWidth, tileHeight, x, y, width, height, repeatMode)
430+
return paintRepeatedBrushImage(w, ref.identifier, tileX, tileY, tileWidth, tileHeight, x, y, width, height, repeatMode, opacity)
427431
})
428432
}
429433

@@ -455,6 +459,20 @@ func normalizeBrushRepeat(image *BrushImageStyle) string {
455459
return strings.TrimSpace(strings.ToLower(image.Repeat))
456460
}
457461

462+
func normalizeBrushOpacity(image *BrushImageStyle) float64 {
463+
if image == nil {
464+
return 1
465+
}
466+
switch {
467+
case image.Opacity <= 0:
468+
return 0
469+
case image.Opacity >= 1:
470+
return 1
471+
default:
472+
return image.Opacity
473+
}
474+
}
475+
458476
func resolveBrushImageSize(fit string, boxWidth, boxHeight, imageWidth, imageHeight float64) (width, height float64) {
459477
if imageWidth <= 0 || imageHeight <= 0 {
460478
return boxWidth, boxHeight
@@ -496,7 +514,7 @@ func resolveBrushAnchor(anchor string, x, y, width, height, contentWidth, conten
496514
}
497515
}
498516

499-
func paintRepeatedBrushImage(w Writer, filename string, tileX, tileY, tileWidth, tileHeight, clipX, clipY, clipWidth, clipHeight float64, repeatMode string) error {
517+
func paintRepeatedBrushImage(w Writer, filename string, tileX, tileY, tileWidth, tileHeight, clipX, clipY, clipWidth, clipHeight float64, repeatMode string, opacity float64) error {
500518
if tileWidth <= 0 || tileHeight <= 0 {
501519
return nil
502520
}
@@ -533,7 +551,7 @@ func paintRepeatedBrushImage(w Writer, filename string, tileX, tileY, tileWidth,
533551
if !repeatX && drawX != startX {
534552
break
535553
}
536-
if err := w.PaintImageFile(filename, drawX, drawY, tileWidth, tileHeight); err != nil {
554+
if err := w.PaintImageFile(filename, drawX, drawY, tileWidth, tileHeight, opacity); err != nil {
537555
return err
538556
}
539557
}

ltml/std_widget_fill_test.go

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type paintedImageCall struct {
1515
y float64
1616
width float64
1717
height float64
18+
opacity float64
1819
}
1920

2021
type backgroundFillTestWriter struct {
@@ -61,13 +62,14 @@ func (w *backgroundFillTestWriter) PaintRadialGradient(rg *pdf.RadialGradient) e
6162
return nil
6263
}
6364

64-
func (w *backgroundFillTestWriter) PaintImageFile(filename string, x, y, width, height float64) error {
65+
func (w *backgroundFillTestWriter) PaintImageFile(filename string, x, y, width, height, opacity float64) error {
6566
w.imagePaints = append(w.imagePaints, paintedImageCall{
6667
filename: filename,
6768
x: x,
6869
y: y,
6970
width: width,
7071
height: height,
72+
opacity: opacity,
7173
})
7274
return nil
7375
}
@@ -226,10 +228,11 @@ func TestStdWidgetPaintBackground_ImageRepeatTilesWithinClip(t *testing.T) {
226228
widget.fill = &BrushStyle{
227229
kind: BrushKindImage,
228230
image: &BrushImageStyle{
229-
Src: "fixture.png",
230-
Fit: "tile",
231-
Anchor: "top-left",
232-
Repeat: "repeat-x",
231+
Src: "fixture.png",
232+
Fit: "tile",
233+
Anchor: "top-left",
234+
Repeat: "repeat-x",
235+
Opacity: 1,
233236
},
234237
}
235238
writer := &backgroundFillTestWriter{
@@ -253,6 +256,92 @@ func TestStdWidgetPaintBackground_ImageRepeatTilesWithinClip(t *testing.T) {
253256
if writer.imagePaints[0].width != 20 || writer.imagePaints[0].height != 10 {
254257
t.Fatalf("tile size = %vx%v, want 20x10", writer.imagePaints[0].width, writer.imagePaints[0].height)
255258
}
259+
if writer.imagePaints[0].opacity != 1 {
260+
t.Fatalf("tile opacity = %v, want 1", writer.imagePaints[0].opacity)
261+
}
262+
}
263+
264+
func TestStdWidgetPaintBackground_ImageOpacityForwardsToPaintCalls(t *testing.T) {
265+
doc, err := Parse([]byte(`<ltml><page /></ltml>`), WithAssetFS(testingMapFS("fixture.png", "image-data")))
266+
if err != nil {
267+
t.Fatal(err)
268+
}
269+
page := doc.Root().Page(0)
270+
widget := &StdWidget{}
271+
if err := widget.SetContainer(page); err != nil {
272+
t.Fatal(err)
273+
}
274+
widget.SetDoc(doc)
275+
widget.SetLeft(5)
276+
widget.SetTop(7)
277+
widget.SetWidth(100)
278+
widget.SetHeight(40)
279+
widget.fill = &BrushStyle{
280+
kind: BrushKindImage,
281+
image: &BrushImageStyle{
282+
Src: "fixture.png",
283+
Fit: "tile",
284+
Anchor: "top-left",
285+
Repeat: "repeat-x",
286+
Opacity: 0.35,
287+
},
288+
}
289+
writer := &backgroundFillTestWriter{
290+
fileDimensions: map[string][2]int{
291+
"fixture.png": {20, 10},
292+
},
293+
}
294+
295+
if err := widget.PaintBackground(writer); err != nil {
296+
t.Fatal(err)
297+
}
298+
if got := len(writer.imagePaints); got != 5 {
299+
t.Fatalf("image paint count = %d, want 5", got)
300+
}
301+
for _, call := range writer.imagePaints {
302+
if call.opacity != 0.35 {
303+
t.Fatalf("tile opacity = %v, want 0.35", call.opacity)
304+
}
305+
}
306+
}
307+
308+
func TestStdWidgetPaintBackground_ImageOpacityZeroSkipsPainting(t *testing.T) {
309+
doc, err := Parse([]byte(`<ltml><page /></ltml>`), WithAssetFS(testingMapFS("fixture.png", "image-data")))
310+
if err != nil {
311+
t.Fatal(err)
312+
}
313+
page := doc.Root().Page(0)
314+
widget := &StdWidget{}
315+
if err := widget.SetContainer(page); err != nil {
316+
t.Fatal(err)
317+
}
318+
widget.SetDoc(doc)
319+
widget.SetLeft(5)
320+
widget.SetTop(7)
321+
widget.SetWidth(100)
322+
widget.SetHeight(40)
323+
widget.fill = &BrushStyle{
324+
kind: BrushKindImage,
325+
image: &BrushImageStyle{
326+
Src: "fixture.png",
327+
Opacity: 0,
328+
},
329+
}
330+
writer := &backgroundFillTestWriter{
331+
fileDimensions: map[string][2]int{
332+
"fixture.png": {20, 10},
333+
},
334+
}
335+
336+
if err := widget.PaintBackground(writer); err != nil {
337+
t.Fatal(err)
338+
}
339+
if got := len(writer.imagePaints); got != 0 {
340+
t.Fatalf("image paint count = %d, want 0", got)
341+
}
342+
if writer.pathCount != 0 || writer.clipCount != 0 {
343+
t.Fatalf("path/clip counts = %d/%d, want 0/0", writer.pathCount, writer.clipCount)
344+
}
256345
}
257346

258347
func TestSample_WidgetBrushBackgrounds_PrintsWithoutWidgetSpecificLogic(t *testing.T) {

ltml/writer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type Writer interface {
4444
// PaintImageFile is the clip-oriented image-as-fill operation used by LTML
4545
// background/text fill code. Unlike generic image placement, callers are
4646
// expected to establish any clipping region before invoking it.
47-
PaintImageFile(filename string, x, y, width, height float64) error
47+
PaintImageFile(filename string, x, y, width, height, opacity float64) error
4848
PaintLinearGradient(lg *pdf.LinearGradient) error
4949
PaintRadialGradient(rg *pdf.RadialGradient) error
5050
PrintParagraph(para []*rich_text.RichText, options options.Options)

0 commit comments

Comments
 (0)