Skip to content

Commit 1e348d2

Browse files
committed
support easy overlay/underlay construction
1 parent 0b99ab0 commit 1e348d2

File tree

6 files changed

+279
-3
lines changed

6 files changed

+279
-3
lines changed

cloudinary-core/src/main/java/com/cloudinary/Transformation.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.regex.Matcher;
1010
import java.util.regex.Pattern;
1111

12+
import com.cloudinary.transformation.AbstractLayerBuilder;
1213
import com.cloudinary.utils.ObjectUtils;
1314
import com.cloudinary.utils.StringUtils;
1415

@@ -132,8 +133,16 @@ public Transformation prefix(String value) {
132133
public Transformation overlay(String value) {
133134
return param("overlay", value);
134135
}
136+
137+
public Transformation overlay(AbstractLayerBuilder<?> value) {
138+
return param("overlay", value);
139+
}
135140

136-
public Transformation underlay(String value) {
141+
public Transformation underlay(Object value) {
142+
return param("underlay", value);
143+
}
144+
145+
public Transformation underlay(AbstractLayerBuilder<?> value) {
137146
return param("underlay", value);
138147
}
139148

@@ -396,8 +405,8 @@ public String generate(Map options) {
396405
}
397406
String width = this.htmlWidth = ObjectUtils.asString(options.get("width"));
398407
String height = this.htmlHeight = ObjectUtils.asString(options.get("height"));
399-
boolean hasLayer = StringUtils.isNotBlank((String) options.get("overlay"))
400-
|| StringUtils.isNotBlank((String) options.get("underlay"));
408+
boolean hasLayer = options.get("overlay") != null && StringUtils.isNotBlank(options.get("overlay").toString())
409+
|| options.get("underlay") != null && StringUtils.isNotBlank(options.get("underlay").toString());
401410

402411
String crop = (String) options.get("crop");
403412
String angle = StringUtils.join(ObjectUtils.asArray(options.get("angle")), ".");
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.cloudinary.transformation;
2+
3+
import java.util.ArrayList;
4+
5+
import com.cloudinary.utils.StringUtils;
6+
7+
public abstract class AbstractLayerBuilder<SELF extends AbstractLayerBuilder<SELF>> {
8+
abstract SELF self();
9+
10+
protected String resourceType = null;
11+
protected String type = null;
12+
protected String publicId = null;
13+
protected String format = null;
14+
15+
public SELF resourceType(String resourceType) {
16+
this.resourceType = resourceType;
17+
return self();
18+
}
19+
20+
public SELF type(String type) {
21+
this.type = type;
22+
return self();
23+
}
24+
25+
public SELF publicId(String publicId) {
26+
this.publicId = publicId.replace('/', ':');
27+
return self();
28+
}
29+
30+
public SELF format(String format) {
31+
this.format = format;
32+
return self();
33+
}
34+
35+
@Override
36+
public String toString() {
37+
ArrayList<String> components = new ArrayList<String>();
38+
39+
if (this.resourceType != null && !this.resourceType.equals("image")) {
40+
components.add(this.resourceType);
41+
}
42+
43+
if (this.type != null && !this.type.equals("upload")) {
44+
components.add(this.type);
45+
}
46+
47+
if (this.publicId == null) {
48+
throw new IllegalArgumentException("Must supply publicId");
49+
}
50+
51+
components.add(formattedPublicId());
52+
53+
return StringUtils.join(components, ":");
54+
}
55+
56+
protected String formattedPublicId() {
57+
String transientPublicId = this.publicId;
58+
59+
if (this.format != null) {
60+
transientPublicId = transientPublicId + "." + this.format;
61+
}
62+
63+
return transientPublicId;
64+
}
65+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.cloudinary.transformation;
2+
3+
public class LayerBuilder extends AbstractLayerBuilder<LayerBuilder> {
4+
@Override
5+
LayerBuilder self() {
6+
return this;
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.cloudinary.transformation;
2+
3+
public class SubtitlesLayerBuilder extends TextLayerBuilder {
4+
public SubtitlesLayerBuilder() {
5+
this.resourceType = "subtitles";
6+
}
7+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package com.cloudinary.transformation;
2+
3+
import java.util.ArrayList;
4+
5+
import com.cloudinary.SmartUrlEncoder;
6+
import com.cloudinary.utils.StringUtils;
7+
8+
public class TextLayerBuilder extends AbstractLayerBuilder<TextLayerBuilder> {
9+
protected String resourceType = "text";
10+
protected String fontFamily = null;
11+
protected Integer fontSize = null;
12+
protected String fontWeight = null;
13+
protected String fontStyle = null;
14+
protected String textDecoration = null;
15+
protected String textAlign = null;
16+
protected String stroke = null;
17+
protected String letterSpacing = null;
18+
protected String text = null;
19+
20+
@Override
21+
TextLayerBuilder self() {
22+
return this;
23+
}
24+
25+
public TextLayerBuilder resourceType(String resourceType) {
26+
throw new UnsupportedOperationException("Cannot modify resourceType for text layers");
27+
}
28+
29+
public TextLayerBuilder type(String type) {
30+
throw new UnsupportedOperationException("Cannot modify type for text layers");
31+
}
32+
33+
public TextLayerBuilder format(String format) {
34+
throw new UnsupportedOperationException("Cannot modify format for text layers");
35+
}
36+
37+
public TextLayerBuilder fontFamily(String fontFamily) {
38+
this.fontFamily = fontFamily;
39+
return self();
40+
}
41+
42+
public TextLayerBuilder fontSize(int fontSize) {
43+
this.fontSize = fontSize;
44+
return self();
45+
}
46+
47+
public TextLayerBuilder fontWeight(String fontWeight) {
48+
this.fontWeight = fontWeight;
49+
return self();
50+
}
51+
52+
public TextLayerBuilder fontStyle(String fontStyle) {
53+
this.fontStyle = fontStyle;
54+
return self();
55+
}
56+
57+
public TextLayerBuilder textDecoration(String textDecoration) {
58+
this.textDecoration = textDecoration;
59+
return self();
60+
}
61+
62+
public TextLayerBuilder textAlign(String textAlign) {
63+
this.textAlign = textAlign;
64+
return self();
65+
}
66+
67+
public TextLayerBuilder stroke(String stroke) {
68+
this.stroke = stroke;
69+
return self();
70+
}
71+
72+
public TextLayerBuilder letterSpacing(String letterSpacing) {
73+
this.letterSpacing = letterSpacing;
74+
return self();
75+
}
76+
77+
public TextLayerBuilder text(String text) {
78+
this.text = SmartUrlEncoder.encode(text).replace("%2C", "%E2%80%9A").replace("/", "%E2%81%84");
79+
return self();
80+
}
81+
82+
@Override
83+
public String toString() {
84+
if (this.publicId == null && this.text == null) {
85+
throw new IllegalArgumentException("Must supply either text or public_id.");
86+
}
87+
88+
ArrayList<String> components = new ArrayList<String>();
89+
components.add(this.resourceType);
90+
91+
String styleIdentifier = textStyleIdentifier();
92+
if (styleIdentifier != null) {
93+
components.add(styleIdentifier);
94+
}
95+
96+
if (this.publicId != null) {
97+
components.add(this.formattedPublicId());
98+
}
99+
100+
if (this.text != null) {
101+
components.add(this.text);
102+
}
103+
104+
return StringUtils.join(components, ":");
105+
}
106+
107+
protected String textStyleIdentifier() {
108+
ArrayList<String> components = new ArrayList<String>();
109+
110+
if (this.fontWeight != null && !this.fontWeight.equals("normal"))
111+
components.add(this.fontWeight);
112+
if (this.fontStyle != null && !this.fontStyle.equals("normal"))
113+
components.add(this.fontStyle);
114+
if (this.textDecoration != null && !this.textDecoration.equals("none"))
115+
components.add(this.textDecoration);
116+
if (this.textAlign != null)
117+
components.add(this.textAlign);
118+
if (this.stroke != null && !this.stroke.equals("none"))
119+
components.add(this.stroke);
120+
if (this.letterSpacing != null)
121+
components.add("letter_spacing_" + this.letterSpacing);
122+
123+
if (this.fontFamily == null && this.fontSize == null && components.isEmpty()) {
124+
return null;
125+
}
126+
127+
if (this.fontFamily == null) {
128+
throw new IllegalArgumentException("Must supply fontFamily.");
129+
}
130+
131+
if (this.fontSize == null) {
132+
throw new IllegalArgumentException("Must supply fontSize.");
133+
}
134+
135+
components.add(0, Integer.toString(this.fontSize));
136+
components.add(0, this.fontFamily);
137+
138+
return StringUtils.join(components, "_");
139+
140+
}
141+
}

cloudinary-core/src/test/java/com/cloudinary/test/CloudinaryTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.cloudinary.Cloudinary;
2222
import com.cloudinary.Transformation;
23+
import com.cloudinary.transformation.*;
2324
import com.cloudinary.utils.ObjectUtils;
2425

2526
public class CloudinaryTest {
@@ -266,6 +267,10 @@ public void testOverlay() {
266267
assertNull(transformation.getHtmlHeight());
267268
assertNull(transformation.getHtmlWidth());
268269
assertEquals(DEFAULT_UPLOAD_PATH + "h_100,l_text:hello,w_100/test", result);
270+
271+
transformation = new Transformation().overlay(new TextLayerBuilder().text("goodbye"));
272+
result = cloudinary.url().transformation(transformation).generate("test");
273+
assertEquals(DEFAULT_UPLOAD_PATH + "l_text:goodbye/test", result);
269274
}
270275

271276
@Test
@@ -897,6 +902,47 @@ public void testAspectRatio() {
897902
assertEquals(DEFAULT_UPLOAD_PATH + "ar_3:2/test", actual);
898903
}
899904

905+
@Test
906+
public void testOverlayOptions() {
907+
Object tests[] = {
908+
new LayerBuilder().publicId("logo"),
909+
"logo",
910+
new LayerBuilder().publicId("folder/logo"),
911+
"folder:logo",
912+
new LayerBuilder().publicId("logo").type("private"),
913+
"private:logo",
914+
new LayerBuilder().publicId("logo").format("png"),
915+
"logo.png",
916+
new LayerBuilder().resourceType("video").publicId("cat"),
917+
"video:cat",
918+
new TextLayerBuilder().text("Hello World, Nice to meet you?").fontFamily("Arial").fontSize(18),
919+
"text:Arial_18:Hello%20World%E2%80%9A%20Nice%20to%20meet%20you%3F",
920+
new TextLayerBuilder().text("Hello World, Nice to meet you?").fontFamily("Arial").fontSize(18)
921+
.fontWeight("bold").fontStyle("italic").letterSpacing("4"),
922+
"text:Arial_18_bold_italic_letter_spacing_4:Hello%20World%E2%80%9A%20Nice%20to%20meet%20you%3F",
923+
new SubtitlesLayerBuilder().publicId("sample_sub_en.srt"), "subtitles:sample_sub_en.srt",
924+
new SubtitlesLayerBuilder().publicId("sample_sub_he.srt").fontFamily("Arial").fontSize(40),
925+
"subtitles:Arial_40:sample_sub_he.srt" };
926+
927+
for (int i = 0; i < tests.length; i += 2) {
928+
Object layer = tests[i];
929+
String expected = (String) tests[i + 1];
930+
assertEquals(expected, layer.toString());
931+
}
932+
}
933+
934+
@Test(expected = IllegalArgumentException.class)
935+
public void testOverlayError1() {
936+
// Must supply font_family for text in overlay
937+
cloudinary.url().transformation(new Transformation().overlay(new TextLayerBuilder().fontStyle("italic"))).generate("test");
938+
}
939+
940+
@Test(expected = IllegalArgumentException.class)
941+
public void testOverlayError2() {
942+
// Must supply public_id for for non-text underlay
943+
cloudinary.url().transformation(new Transformation().underlay(new LayerBuilder().resourceType("video"))).generate("test");
944+
}
945+
900946
public static Map<String, String> getUrlParameters(URI uri) throws UnsupportedEncodingException {
901947
Map<String, String> params = new HashMap<String, String>();
902948
for (String param : uri.getRawQuery().split("&")) {

0 commit comments

Comments
 (0)